package com.vcolco.skill.impl;


import cn.hutool.core.util.RandomUtil;
import com.vcolco.skill.Singleton;
import com.vcolco.skill.SkillUtil;
import com.vcolco.skill.SpringContextUtil;
import com.vcolco.utils.RedisUtils;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.*;

@Service("skillUtil")
public abstract class SkillUtilInvoke implements SkillUtil {

    private RedisUtils redisUtils = (RedisUtils) SpringContextUtil.getBean("redisUtils");

    //秒杀初始化锁
    private String skillLock = "skill_lock_";
    //秒杀用户等待队列
    private String skillUser = "skill_wait_user_";
    //秒杀成功用户
    private String successUser = "success_user_";

    @Override
    public boolean run(String skillName,String userToken, int skillSize, Date overTime,int minSize,int maxSize,int ratio){
        //1. 初始化容器
        init(skillName,skillSize,overTime);
        //2. 内部逻辑秒杀
        boolean pushSuccess = peakCheck(skillName, userToken, minSize, maxSize, ratio, overTime);
        //3. 判断是否真的秒杀成功
        String uid = null;
        if (pushSuccess)  uid = queryResult(skillName, userToken);
        if (null == uid) return false;
        //4. 如果能进行秒杀逻辑处理 则调用抽象方法进行逻辑处理
        boolean skillSuccess = false;
        try {
            skillSuccess = skill();
        }catch (Exception e){
            recoToken(skillName,uid,userToken);
            return false;
        }
        if (!skillSuccess) recoToken(skillName,uid,userToken);
        return skillSuccess;
    }

    // 初始化
    private void init(String skillName, int skillSize, Date overTime){
        //判断是否需要初始化
        boolean isInit = Singleton.getInstance().setkey(skillName);
        if (!isInit) return;
        //判断是否活动已经过期
        long mill = overTime.getTime() - new Date().getTime();
        if (mill < 1) return;
        //判断是否已经初始化
        boolean skillIs = redisUtils.setnx( skillLock + skillName, "skill", mill);
        if (!skillIs) return;
        List<Object> list = new ArrayList<>();
        for (int i =0;i < skillSize; i++){
            list.add(RandomUtil.randomString(10));
        }
        redisUtils.rpushAll(skillName,list);
    }

    /**
     * @title 消峰秒杀
     * @describe 这里秒杀峰值指的是每分钟该接口请求次数 超过该峰值 则返回false 没有超过则返回true
     * 并且设置超时时间为60秒 一分钟内不可多次请求
     * @param
     * @param maxValue 峰值
     * @return
     */
    public boolean peakCheck(String skillName,String userToken,int minValue,int maxValue,int ratio,Date overTime){
        String nowDate = Singleton.getInstance().getTime();
        long incrNum = redisUtils.incr(skillName+"-ss-"+nowDate);
        // 如果超过最大阈值则直接返回false 不进行数据处理
        if(incrNum > maxValue) return false;
        // 如果没有超过最大阈值 且大于 最小阈值 启动限流
        if (incrNum > minValue){
            boolean isIntercept = 1 == (int) (1 + Math.random() * ratio)?false:true;
            // 如果需要拦截 则返回false
            if (isIntercept){
                return false;
            }
        }
        redisUtils.expire(skillName+"-ss-"+nowDate,1l);
        long nowTimes = new Date().getTime();
        long overTimes = overTime.getTime();
        // 修改上面的逻辑 不放入队列中 防止被同一用户刷库
        boolean wait = redisUtils.setnx(skillUser + skillName + "_" + userToken, "wait", overTimes - nowTimes);
        if (!wait) return false;
        //启动秒杀系统
        return skill3(skillName,userToken,overTimes-nowTimes);
    }


    /**
     * 秒杀3.0
     * @param skillName
     * @param userToken
     * @param overTime
     * @return
     */
    public boolean skill3(String skillName,String userToken, long overTime){
        boolean isDel = redisUtils.delete(skillUser + skillName + "_" + userToken);
        if (!isDel) return false;
        Object skillToken = redisUtils.lpop(skillName);
        if (null == skillToken) return false;
        boolean skillSuccess = redisUtils.setnx(successUser+skillName+"_"+userToken, skillToken, overTime);
        if (skillSuccess) return true;
        redisUtils.rpush(skillName,skillToken);
        return false;
    }

    /**
     * 查询单个用户秒杀结果
     * @param
     * @param token
     * @return
     */
    public String queryResult(String skillName, String token){
        return redisUtils.get(successUser + skillName + "_" + token);
    }

    /**
     * Title:令牌回收/添加令牌
     * Description: 对错误发放令牌的用户 回收令牌 & 也可以对现有的令牌池添加令牌
     * Company: vcolco
     *
     * @author gujun
     * @date 2019年7月23日11:11:36
     */
    public boolean recoToken(String skillName,String skillToken,String userToken){
        redisUtils.delete(successUser+skillName+"_"+userToken);
        redisUtils.rpush(skillName,skillToken);
        return true;
    }

    /**
     * 使用人员需要实现的逻辑 逻辑判断是否秒杀成功 若返回false 或报错时 将会自动回收令牌
     * @return
     */
    public abstract boolean skill();
}
